package com.hys.app.service.erp.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hys.app.framework.database.WebPage;
import com.hys.app.framework.exception.ServiceException;
import com.hys.app.framework.rabbitmq.MessageSender;
import com.hys.app.framework.rabbitmq.MqMessage;
import com.hys.app.framework.util.BeanUtil;
import com.hys.app.framework.util.DateUtil;
import com.hys.app.framework.util.PageConvert;
import com.hys.app.framework.util.StringUtil;
import com.hys.app.mapper.erp.StockDamageReportMapper;
import com.hys.app.model.base.rabbitmq.AmqpExchange;
import com.hys.app.model.erp.dos.StockDamageReport;
import com.hys.app.model.erp.dos.StockDamageReportProduct;
import com.hys.app.model.erp.dto.StockDamageReportDTO;
import com.hys.app.model.erp.dto.StockDamageReportParam;
import com.hys.app.model.erp.dto.StockDamageReportStatisticsParam;
import com.hys.app.model.erp.dto.StockUpdateDTO;
import com.hys.app.model.erp.dto.message.StockDamageAuditPassMessage;
import com.hys.app.model.erp.enums.NoBusinessTypeEnum;
import com.hys.app.model.erp.enums.StockChangeSourceEnum;
import com.hys.app.model.erp.enums.StockDamageReportStatusEnum;
import com.hys.app.model.erp.enums.StockOperateEnum;
import com.hys.app.model.erp.vo.StockDamageReportAllowable;
import com.hys.app.model.erp.vo.StockDamageReportStatistics;
import com.hys.app.model.erp.vo.StockDamageReportVO;
import com.hys.app.service.erp.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 库存报损单业务接口实现类
 *
 * @author dmy
 * 2023-12-05
 */
@Service
public class StockDamageReportManagerImpl extends ServiceImpl<StockDamageReportMapper, StockDamageReport> implements StockDamageReportManager {

    @Autowired
    private NoGenerateManager noGenerateManager;

    @Autowired
    private StockDamageReportProductManager stockDamageReportProductManager;

    @Autowired
    private StockDamageReportMapper stockDamageReportMapper;

    @Autowired
    private MessageSender messageSender;

    @Autowired
    private WarehouseEntryBatchManager warehouseEntryBatchManager;

    /**
     * 查询库存报损单分页列表数据
     *
     * @param params 查询参数
     * @return
     */
    @Override
    public WebPage list(StockDamageReportParam params) {
        IPage<StockDamageReport> iPage = this.lambdaQuery()
                .like(StringUtil.notEmpty(params.getSn()), StockDamageReport::getSn, params.getSn())
                .eq(params.getWarehouseId() != null, StockDamageReport::getWarehouseId, params.getWarehouseId())
                .eq(StringUtil.notEmpty(params.getStatus()), StockDamageReport::getStatus, params.getStatus())
                .eq(params.getDeptId() != null, StockDamageReport::getDeptId, params.getDeptId())
                .gt(params.getStartTime() != null, StockDamageReport::getReportTime, params.getStartTime())
                .lt(params.getEndTime() != null, StockDamageReport::getReportTime, params.getEndTime())
                .orderByDesc(StockDamageReport::getCreateTime)
                .page(new Page<>(params.getPageNo(), params.getPageSize()));

        for (StockDamageReport record : iPage.getRecords()) {
            StockDamageReportAllowable allowable = new StockDamageReportAllowable(record);
            record.setAllowable(allowable);
        }
        return PageConvert.convert(iPage);
    }

    /**
     * 新增库存报损单
     *
     * @param stockDamageReportDTO 库存报损单信息
     */
    @Override
    @Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void add(StockDamageReportDTO stockDamageReportDTO) {
        //构建报损单信息
        StockDamageReport stockDamageReport = new StockDamageReport();
        BeanUtil.copyProperties(stockDamageReportDTO, stockDamageReport);
        //生成库存报损单编号
        String sn = noGenerateManager.generate(NoBusinessTypeEnum.StockDamageReport);
        stockDamageReport.setSn(sn);
        //设置创建时间
        stockDamageReport.setCreateTime(DateUtil.getDateline());
        //设置状态默认为未提交（待审核）
        stockDamageReport.setStatus(StockDamageReportStatusEnum.NEW.name());
        //入库
        this.save(stockDamageReport);
        //获取主键ID
        Long id = stockDamageReport.getId();
        //保存库存报损单商品信息
        this.stockDamageReportProductManager.saveProduct(id, stockDamageReportDTO.getProductList());
    }

    /**
     * 编辑库存报损单
     *
     * @param id                   主键ID
     * @param stockDamageReportDTO 库存报损单信息
     */
    @Override
    @Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void edit(Long id, StockDamageReportDTO stockDamageReportDTO) {
        //获取库存报损单旧数据
        StockDamageReport stockDamageReport = this.getById(id);
        if (!StockDamageReportStatusEnum.NEW.name().equals(stockDamageReport.getStatus())
                && !StockDamageReportStatusEnum.REJECT.name().equals(stockDamageReport.getStatus())) {
            throw new ServiceException("只有状态为未提交或审核驳回的库存报损单才可以进行编辑操作");
        }
        //复制修改后的数据
        BeanUtil.copyProperties(stockDamageReportDTO, stockDamageReport);
        //编辑后默认库存报损单状态为未提交（待审核）
        stockDamageReport.setStatus(StockDamageReportStatusEnum.NEW.name());
        //修改库存报损单信息
        this.updateById(stockDamageReport);
        //入库库存报损单商品
        this.stockDamageReportProductManager.saveProduct(id, stockDamageReportDTO.getProductList());
    }

    /**
     * 删除库存报损单
     *
     * @param ids 库存报损单主键ID集合
     */
    @Override
    @Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void delete(List<Long> ids) {
        List<StockDamageReport> reports = this.lambdaQuery()
                .in(StockDamageReport::getId, ids)
                .list();
        if (reports == null || reports.size() == 0) {
            throw new ServiceException("要删除的库存报损单不存在");
        }
        //验证库存报损单状态是否可以删除
        for (StockDamageReport report : reports) {
            if (!StockDamageReportStatusEnum.NEW.name().equals(report.getStatus())
                    && !StockDamageReportStatusEnum.REJECT.name().equals(report.getStatus())) {
                throw new ServiceException("只有状态为未提交或审核驳回的库存报损单才可进行删除操作");
            }
        }
        //批量删除库存报损单
        this.removeByIds(ids);
        //删除库存报损单商品信息
        this.stockDamageReportProductManager.deleteProduct(ids);
    }

    /**
     * 获取库存报损单详情
     *
     * @param id 主键ID
     * @return
     */
    @Override
    public StockDamageReportVO getDetail(Long id) {
        StockDamageReport stockDamageReport = this.getById(id);
        StockDamageReportVO stockDamageReportVO = new StockDamageReportVO();
        BeanUtil.copyProperties(stockDamageReport, stockDamageReportVO);

        //获取库存报损单商品信息
        List<StockDamageReportProduct> goodsList = this.stockDamageReportProductManager.list(id);
        stockDamageReportVO.setProductList(goodsList);
        return stockDamageReportVO;
    }

    /**
     * 库存报损单提交审核
     *
     * @param ids 库存报损单主键ID集合
     */
    @Override
    @Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void submit(List<Long> ids) {
        List<StockDamageReport> reports = this.lambdaQuery()
                .in(StockDamageReport::getId, ids)
                .list();
        if (reports == null || reports.size() == 0) {
            throw new ServiceException("要提交审核的库存报损单不存在");
        }
        //验证库存报损单状态是否可以提交审核
        for (StockDamageReport report : reports) {
            if (!StockDamageReportStatusEnum.NEW.name().equals(report.getStatus())) {
                throw new ServiceException("只有状态为未提交的库存报损单才可进行提交审核操作");
            }
        }
        //将库存报损单状态修改为已提交（待审核）
        this.lambdaUpdate()
                .set(StockDamageReport::getStatus, StockDamageReportStatusEnum.WAIT.name())
                .in(StockDamageReport::getId, ids)
                .update();
    }

    /**
     * 库存报损单撤销提交审核
     *
     * @param ids 库存报损单主键ID集合
     */
    @Override
    @Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void cancel(List<Long> ids) {
        List<StockDamageReport> reports = this.lambdaQuery()
                .in(StockDamageReport::getId, ids)
                .list();
        if (reports == null || reports.size() == 0) {
            throw new ServiceException("要撤销审核的库存报损单不存在");
        }
        //验证库存报损单状态是否可以撤销审核
        for (StockDamageReport report : reports) {
            if (!StockDamageReportStatusEnum.WAIT.name().equals(report.getStatus())) {
                throw new ServiceException("只有状态为已提交的库存报损单才可进行撤销操作");
            }
        }
        //将库存报损单状态修改为未提交
        this.lambdaUpdate()
                .set(StockDamageReport::getStatus, StockDamageReportStatusEnum.NEW.name())
                .in(StockDamageReport::getId, ids)
                .update();
    }

    /**
     * 审核库存报损单
     *
     * @param ids          库存报损单ID集合
     * @param status       审核状态 PASS：审核通过，REJECT：审核驳回
     * @param rejectReason 驳回原因
     */
    @Override
    @Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void audit(List<Long> ids, String status, String rejectReason) {
        List<StockDamageReport> reports = this.lambdaQuery()
                .in(StockDamageReport::getId, ids)
                .list();
        if (reports == null || reports.size() == 0) {
            throw new ServiceException("要审核的库存报损单不存在");
        }
        if (!StockDamageReportStatusEnum.PASS.name().equals(status)
                && !StockDamageReportStatusEnum.REJECT.name().equals(status)) {
            throw new ServiceException("审核状态不正确");
        }
        if (StockDamageReportStatusEnum.REJECT.name().equals(status)) {
            if (StringUtil.isEmpty(rejectReason)) {
                throw new ServiceException("请填写审核驳回原因");
            }
            if (rejectReason.length() > 200) {
                throw new ServiceException("驳回原因不能超过200个字符");
            }
        }
        //循环审核库存报损单
        for (StockDamageReport report : reports) {
            if (!StockDamageReportStatusEnum.WAIT.name().equals(report.getStatus())) {
                throw new ServiceException("只有状态为待审核的库存报损单才可以进行审核操作");
            }
        }

        //修改库存报损单状态
        this.lambdaUpdate()
                .set(StockDamageReport::getStatus, status)
                .set(StringUtil.notEmpty(rejectReason), StockDamageReport::getRejectReason, rejectReason)
                .set(StockDamageReport::getAuditTime, DateUtil.getDateline())
                .in(StockDamageReport::getId, ids)
                .update();

        // 如果审核通过
        if (StockDamageReportStatusEnum.PASS.name().equals(status)) {
            for (StockDamageReport report : reports) {
                List<StockDamageReportProduct> transferProductList = stockDamageReportProductManager.list(report.getId());

                List<StockUpdateDTO> updateList = new ArrayList<>();
                for (StockDamageReportProduct product : transferProductList) {
                    StockUpdateDTO stockUpdateDTO = new StockUpdateDTO();
                    stockUpdateDTO.setBatchId(product.getBatchId());
                    stockUpdateDTO.setTip(product.getBatchSn());
                    stockUpdateDTO.setChangeNum(product.getReportNum());
                    stockUpdateDTO.setOperate(product.getType() == 0 ? StockOperateEnum.Increase : StockOperateEnum.Reduce);
                    updateList.add(stockUpdateDTO);
                }
                this.warehouseEntryBatchManager.updateStock(StockChangeSourceEnum.STOCK_DAMAGE, report.getSn(), updateList);
            }

            // 发送审核通过消息
            StockDamageAuditPassMessage message = new StockDamageAuditPassMessage();
            message.setList(reports);
            this.messageSender.send(new MqMessage(AmqpExchange.STOCK_DAMAGE_AUDIT_PASS, AmqpExchange.STOCK_DAMAGE_AUDIT_PASS + "_ROUTING", message));
        }

    }

    /**
     * 查询库存报损单商品统计分页列表数据
     *
     * @param params 查询参数
     * @return
     */
    @Override
    public WebPage statistics(StockDamageReportStatisticsParam params) {
        IPage<StockDamageReportStatistics> iPage = this.stockDamageReportMapper.selectReportPage(new Page(params.getPageNo(), params.getPageSize()), params);
        return PageConvert.convert(iPage);
    }

    /**
     * 导出库存报损单商品统计列表
     *
     * @param response
     * @param params   查询参数
     */
    @Override
    public void export(HttpServletResponse response, StockDamageReportStatisticsParam params) {
        //查询库存报损单商品列表
        List<StockDamageReportStatistics> list = this.stockDamageReportMapper.selectReportList(params);

        ArrayList<Map<String, Object>> rows = CollUtil.newArrayList();
        ExcelWriter writer = ExcelUtil.getWriter(true);
        for (StockDamageReportStatistics statistics : list) {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("库存报损单号", statistics.getSn());
            map.put("报损时间", DateUtil.toString(statistics.getReportTime(), "yyyy-MM-dd HH:mm:ss"));
            map.put("入库单号", statistics.getStockSn());
            map.put("商品编号", statistics.getProductSn());
            map.put("商品名称", statistics.getProductName());
            map.put("商品类别", statistics.getCategoryName());
            map.put("规格型号", statistics.getSpecification());
            map.put("单位", statistics.getUnit());
            map.put("报损类型", statistics.getDamageTypeKey());
            map.put("报损数量", statistics.getReportNum());
            rows.add(map);
        }

        writer.write(rows, true);

        ServletOutputStream out = null;
        try {
            out = response.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        response.setHeader("Content-Disposition", "attachment;filename=stock_damage_report.xlsx");
        writer.flush(out, true);
        writer.close();
        IoUtil.close(out);
    }
}
